package com.globalwave.pos.purchase.service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.globalwave.base.BaseServiceImpl;
import com.globalwave.common.ArrayOrderList;
import com.globalwave.common.ArrayPageList;
import com.globalwave.common.C;
import com.globalwave.common.U;
import com.globalwave.common.cache.CodeHelper;
import com.globalwave.common.exception.BusinessException;
import com.globalwave.pos.POSCodeHelper;
import com.globalwave.pos.inventory.entity.ProductItem;
import com.globalwave.pos.inventory.entity.ProductItemSO;
import com.globalwave.pos.payment.entity.PaymentLine;
import com.globalwave.pos.payment.entity.PaymentMethod;
import com.globalwave.pos.payment.service.Payable;
import com.globalwave.pos.payment.service.PaymentBO;
import com.globalwave.pos.purchase.entity.PurchasesOrder;
import com.globalwave.pos.purchase.entity.PurchasesOrderLine;
import com.globalwave.pos.purchase.entity.PurchasesOrderSO;
import com.globalwave.system.entity.SessionUser;


@Service("purchasesOrderBO")
@Scope("prototype")
@Transactional
public class PurchasesOrderBO extends BaseServiceImpl implements Payable{

	
	private PurchasesOrder order = null ;
	private PurchasesOrderLineBO purchasesOrderLineBO ;
	private PaymentBO paymentBO = null;

	public PurchasesOrder newOrder() throws Exception {
		order = new PurchasesOrder() ;
		order.setPayment_progrss(C.PROGRSS_NOT_START) ;
		order.setPayment_times(C.ZERO_SHORT) ;

		order.setOriginal_price(C.ZERO_DOUBLE);
		order.setDiscount_price(C.ZERO_DOUBLE) ;
		order.setPrice_1(C.ZERO_DOUBLE) ;
		order.setPrice_2(C.ZERO_DOUBLE) ;
		order.setNation_tax(C.ZERO_DOUBLE) ;
		order.setLocation_tax(C.ZERO_DOUBLE) ;
		order.setRecievable_amount(C.ZERO_DOUBLE) ;
		order.setRecieved_amount(C.ZERO_DOUBLE) ;
		
		
		order.setShipment_progrss(C.PROGRSS_NOT_START) ;
		order.setShipment_times(C.ZERO_SHORT) ;
		order.setShipped_quantity(C.ZERO_INTEGER) ;
		order.setTna_quantity(C.ZERO_INTEGER) ;
		order.setOrder_quantity(C.ZERO_INTEGER) ;
		
		order.setOrder_progrss(C.PROGRSS_NOT_START) ;
		
		SessionUser user = SessionUser.get() ;
		order.setWarehouse_code(user.getShop_code()) ;
		order.setShop_code(user.getShop_code()) ;
		order.setTerminal_code(user.getTerminal_code()) ;
		order.setRegion_code(user.getUser().getRegion_code()) ;
		order.setStaff_number(user.getUser().getLogin_id()) ;
		order.setSale_date(user.getSale_date()) ;
		order.setOrder_timestamp(U.currentTimestamp()) ;
		
		order.setStatus_(C.STATUS_ACTIVE) ;
		
		return this.order ;
	}
	
	public PurchasesOrder get(PurchasesOrder order) throws Exception {
		this.order = (PurchasesOrder) jdbcDao.find(order) ;
		
		return this.order;
	}
	
	public PurchasesOrder getWithLines(PurchasesOrder order) throws Exception {
		this.order = (PurchasesOrder) jdbcDao.find(order) ;
		
		if (this.order == null) {
			throw new BusinessException("") ;
		}
		
		Long orderId = this.order.getId() ;
		List<PurchasesOrderLine> lines = getPurchasesOrderLineBO().getLines(orderId) ;
		this.order.setLines(lines);
		
		for (PurchasesOrderLine line:lines) {
			line.setPurchasesOrder(this.order) ;
		}

		List<PaymentLine> payments = getPaymentBO().getLinesByPurchasesOrder(orderId) ;
		this.order.setPaymentLines(payments) ;
		
		return this.order;
	}
	
	
	public ArrayPageList<PurchasesOrder> query(
			PurchasesOrderSO criterion,
            int pageIndex, 
            int pageSize, 
            ArrayOrderList orderList) throws Exception {
        
        if (orderList == null) {
            orderList = new ArrayOrderList() ;
            orderList.addDesc("id") ;
        }
        
        return (ArrayPageList<PurchasesOrder>)jdbcDao.query(criterion, pageIndex, pageSize, orderList, PurchasesOrder.class);
	}

	public PurchasesOrder addItem(PurchasesOrder order, ProductItemSO itemSo) throws Exception {
		ProductItem item = new ProductItem() ;
		item.setItem_number(itemSo.getItem_number()) ;
		
		return addItem(order, item, itemSo.getOrder_quantity()) ;
	}
	
	public PurchasesOrder addItem(PurchasesOrder order, ProductItem item, Integer quantity) throws Exception {
		
		if (order == null) {
			newOrder() ;
		} else {
			this.order = order ;
		}
		
		getPurchasesOrderLineBO().add(item, quantity) ;
		
		return this.order;
	}
	
	
	public void comfirm(PurchasesOrder order) throws Exception  {
		this.order = order ;
		this.jdbcDao.insert(order) ;
		
		getPurchasesOrderLineBO() ;
		
		for (PurchasesOrderLine line:order.getLines()) {
			purchasesOrderLineBO.confirm(line) ;
		}
		
	}
	

	public void recomfirm(PurchasesOrder order) throws Exception  {
		this.order = order ;
	}


	public void ship(PurchasesOrder shipmentOrder) throws Exception  {
		
		if (this.order == null) {
		    this.order = get(shipmentOrder) ;// renew order from database
		}
		
		this.order.setOperate(PurchasesOrder.OPERATE_UPDATE) ;
		
		this.order.setShipment_times(
				(short)(this.order.getShipment_times().shortValue() + 1)) ;
		
		int quantity = this.order.getShipped_quantity() ;
		int tna = this.order.getTna_quantity() ;

		if (this.order.getOrder_quantity() == (quantity + tna)) {
			order.setShipment_progrss(C.PROGRSS_END) ;

		    if (order.getPayment_progrss() == C.PROGRSS_END) {
		    	order.setOrder_progrss(C.PROGRSS_END) ;
		    }
		}
		
		if (order.getShipment_progrss() == C.PROGRSS_NOT_START) {
			order.setShipment_progrss(C.PROGRSS_START) ;
		}
		
		
		if (this.order.getLines().size() != shipmentOrder.getLines().size()) {
			throw new BusinessException("lines.size.not.eqaul") ;
		}

		List<PurchasesOrderLine> lines = this.order.getLines() ;
		
		List<PurchasesOrderLine> shipmentsLines = shipmentOrder.getLines() ;
		
		for (int i = shipmentsLines.size() - 1; i >= 0 ; i --) {
			purchasesOrderLineBO.ship(lines.get(i), shipmentsLines.get(i)) ;// if all of lines shiped
		}
		
		this.jdbcDao.update(this.order) ;
	}

	public void pay(PurchasesOrder paymentOrder) throws Exception  {
		
		getPaymentBO();
		
		List<PaymentLine> paymentLines = new ArrayList<PaymentLine>() ;
		order.setPaymentLines(paymentLines) ;
		
		order.setPayment_times((short)(order.getPayment_times().intValue() + 1)) ;
		
		int i = 0 ;
		for (PaymentLine line:paymentOrder.getPaymentLines()) {
			if (line.getPayment_amount() == null || line.getPayment_amount() == 0) {
				continue ;
			}
			line.setRef_source(C.REF_SOURCE_SALES_ORDER) ;
			line.setRef_id(this.order.getId()) ;
			line.setLine_number(i ++) ;
			paymentLines.add(line) ;
			paymentBO.add(line, this) ;
		}
		
		// 检查是否已经完成付款操作
		if (this.order.getRecievable_amount().doubleValue() 
				== this.order.getRecieved_amount().doubleValue()) {
		    
			PaymentAmount pAmount = new PaymentAmount() ;
			
			for (PurchasesOrderLine line:order.getLines())  {
				pAmount.add(
						line.getCurrency_code(), 
						line.getUnit_discount_price() * (line.getOrder_quantity() - line.getTna_quantity())) ;
			}
			
			for (PaymentLine line:paymentLines)  {
				pAmount.deduct(line.getCurrency_code(), line.getPayment_amount()) ;
			}
			
			if (pAmount.isAllPaid()) {
			    order.setPayment_progrss(C.PROGRSS_END) ;
			    if (order.getShipment_progrss() == C.PROGRSS_END) {
			    	order.setOrder_progrss(C.PROGRSS_END) ;
			    }
			}
		}
		
		// ------
		if (order.getPayment_progrss() == C.PROGRSS_NOT_START) {
			order.setPayment_progrss(C.PROGRSS_START) ;
		}
		
		jdbcDao.update(this.order) ;
	}

	public void autoShip() throws Exception  {
		PurchasesOrder shipmentOrder = this.order.clone() ;
		
		List<PurchasesOrderLine> lines = new ArrayList<PurchasesOrderLine>(order.getLines().size()) ;
		shipmentOrder.setLines(lines) ;
		
		for (PurchasesOrderLine line:order.getLines()) {
			PurchasesOrderLine sLine = line.clone() ;
			lines.add(sLine) ;
			
			purchasesOrderLineBO.autoShip(sLine) ;
		}
		
		ship(shipmentOrder) ;
	}
	
	/**
	 * line中产品增加的数量对订单的影响更新
	 * 
	 * @param line
	 * @param quantity
	 * @throws Exception
	 */
	public void updateByLine(PurchasesOrderLine line, int quantity) throws Exception  {

		//this.order.getLines().add(line) ;

		if (!POSCodeHelper.isUseInnerAccount(line.getCurrency_code())) {
			order.setOriginal_price(order.getOriginal_price() + line.getUnit_orginal_price() * quantity);
			order.setDiscount_price(order.getDiscount_price() + line.getUnit_discount_price() * quantity) ;
			order.setPrice_1(order.getPrice_1() + line.getUnit_price1() * quantity) ;
			order.setPrice_2(order.getPrice_2() + line.getUnit_price1() * quantity) ;
			order.setNation_tax(order.getNation_tax() + line.getUnit_nation_tax() * quantity) ;
			order.setLocation_tax(order.getLocation_tax() + line.getUnit_location_tax() * quantity) ;
			order.setRecievable_amount(order.getRecievable_amount() + line.getRecievable_amount()) ;	
		}
	}

	public void updateByPaymentLine(PaymentLine line) throws Exception  {

		if (!POSCodeHelper.isUseInnerAccount(line.getCurrency_code())) {
			if (line.getPayment_amount() != null) {				
				order.setRecieved_amount(order.getRecieved_amount() + line.getPayment_amount()) ;
			}
		}
	}
	/**
	 * line中产品增加的数量对订单的影响更新
	 * 
	 * @param line
	 * @param quantity
	 * @throws Exception
	 */
	public void updateByShipmentLine(PurchasesOrderLine line) throws Exception  {
		
		int quantity = line.getShipped_quantity() ;
		int tna = line.getTna_quantity() ;
		
		order.setShipped_quantity(order.getShipped_quantity() + quantity);
		order.setTna_quantity(order.getTna_quantity() + tna) ;
	}
	
	public void cancel(PurchasesOrder order)throws Exception  {

		SessionUser user = SessionUser.get() ;
		
		// cancel payment
		PurchasesOrderLine payment = new PurchasesOrderLine() ;
		payment.setStatus_(C.STATUS_CANCEL) ;
		payment.addInclusions("status_") ;
		
		PurchasesOrderLine paymentCriterion = new PurchasesOrderLine() ;
		paymentCriterion.setPurchases_order_id(order.getId()) ;
		
		this.jdbcDao.update(payment, paymentCriterion) ;
		
		// cancel line
		PurchasesOrderLine line = new PurchasesOrderLine() ;
		line.setStatus_(C.STATUS_CANCEL) ;
		line.addInclusions("status_") ;
		
		PurchasesOrderLine criterion = new PurchasesOrderLine() ;
		criterion.setPurchases_order_id(order.getId()) ;
		
		this.jdbcDao.update(line, criterion) ;
		
		// cancel header
		this.order.setStatus_(C.STATUS_CANCEL) ;		
		this.order.setCancel_region_code(user.getUser().getRegion_code()) ;
		this.order.setCancel_shop_code(user.getShop_code()) ;
		this.order.setCancel_terminal_code(user.getTerminal_code()) ;
		this.order.setCancel_staff_number(user.getUser().getLogin_id()) ;
		this.order.setCancel_sale_date(user.getSale_date()) ;
		this.order.setCancel_timestamp(U.currentTimestamp()) ;
		
		this.jdbcDao.update(this.order) ;
	}
	
	public List<PaymentLine> getPayementMethods(PurchasesOrder order) throws Exception {
		this.order = order ;
		// get line currency code 
		Set<String> codes = new HashSet<String>() ;
		for (PurchasesOrderLine line:this.order.getLines()) {
			codes.add(line.getCurrency_code()) ;
		}
		
		// get payment metohd by currency
		List<PaymentMethod> methods = CodeHelper.query("PAYMENT_METHOD", "currency_code", codes, PaymentMethod.class) ;
		List<PaymentLine> lines = new ArrayList<PaymentLine>() ;
		
		for (PaymentMethod method:methods) {
			PaymentLine line = new PaymentLine() ;
			lines.add(line);
			line.setCurrency_code(method.getCurrency_code()) ;
			line.setPayment_method(method.getCode_()) ;
			line.setPayment_method_name(method.getName_()) ;
		}
		
		return lines ;
	}

	public void payAndAutoComplete(PurchasesOrder order, PurchasesOrder paymentOrder) throws Exception {
		this.order = order ;
		comfirm(order) ;
		pay(paymentOrder) ;
		autoShip() ;
	}

	public PurchasesOrder getOrder() {
		return order;
	}



	public void setOrder(PurchasesOrder order) {
		this.order = order;
	}



	public PurchasesOrderLineBO getPurchasesOrderLineBO() {
		if (purchasesOrderLineBO == null) {
			purchasesOrderLineBO = new PurchasesOrderLineBO() ;
			purchasesOrderLineBO.setJdbcDao(jdbcDao) ;
		    purchasesOrderLineBO.setPurchasesOrderBO(this) ;
		}
		return purchasesOrderLineBO;
	}



	public void setPurchasesOrderLineBO(PurchasesOrderLineBO purchasesOrderLineBO) {
		this.purchasesOrderLineBO = purchasesOrderLineBO;
	}
	
	 

	public PaymentBO getPaymentBO() {
		if (paymentBO == null) {
			paymentBO = (PaymentBO) CodeHelper.getAppContext().getBean("paymentBO") ;
		}
		return paymentBO;
	}

	public void setPaymentBO(PaymentBO paymentBO) {
		this.paymentBO = paymentBO;
	}
	
	private class PaymentAmount extends HashMap<String, Double> {
		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;

		public void add(String currencyCode, Double amount) {
			Double remain = this.get(currencyCode) ;
			
			if (remain == null) {
			    this.put(currencyCode, amount) ;
			} else {
				this.put(currencyCode, remain + amount) ;
			}
		}
		
		public void deduct(String currencyCode, Double amount) {
			Double remain = this.get(currencyCode) ;
			
			if (remain == null) {
			    this.put(currencyCode, -amount) ;
			} else {
				this.put(currencyCode, remain - amount) ;
			}
		}

        public boolean isAllPaid() {
        	for (double amount:this.values()) {
        		if (amount != 0) {
        			return false ;
        		}
        	}
        	return true ;
        }
	}

	@Override
	public void updateByPayment(List<PaymentLine> paymentLines,
			boolean isAllPaid) throws Exception {
		// TODO Auto-generated method stub
		
	}
	
}
